﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;

namespace AIStudio.Wpf.DiagramDesigner.Geometrys
{
    [Serializable]
    public struct PointBase : IFormattable
    {
        public static PointBase Zero { get; } = new PointBase(0, 0);

        public PointBase(double x, double y)
        {
            _x = x;
            _y = y;
        }

        public PointBase(Point point)
        {
            _x = point.X;
            _y = point.Y;
        }

        /// <summary>
        /// 中间X
        /// </summary>
        internal double _x;
        public double X
        {
            get
            {
                return _x;
            }

            set
            {
                _x = value;
            }
        }

        /// <summary>
        /// 中间Y
        /// </summary>      
        internal double _y;
        public double Y
        {
            get
            {
                return _y;
            }

            set
            {
                _y = value;
            }
        }

        public double Dot(PointBase other) => X * other.X + Y * other.Y;

        public PointBase Lerp(PointBase other, double t)
            => new PointBase(X * (1.0 - t) + other.X * t, Y * (1.0 - t) + other.Y * t);

        // Maybe just make Points mutable?
        public PointBase Add(double value) => new PointBase(X + value, Y + value);
        public PointBase Add(double x, double y) => new PointBase(X + x, Y + y);

        public PointBase Substract(double value) => new PointBase(X - value, Y - value);
        public PointBase Substract(double x, double y) => new PointBase(X - x, Y - y);

        public double DistanceTo(PointBase other)
            => Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2));

        public void Deconstruct(out double x, out double y)
        {
            x = X;
            y = Y;
        }

        //public static PointBase operator -(PointBase a, PointBase b)
        //{
        //    return new PointBase(a.X - b.X, a.Y - b.Y);
        //}
        //public static PointBase operator +(PointBase a, PointBase b)
        //{
        //    return new PointBase(a.X + b.X, a.Y + b.Y);
        //}

        public static implicit operator PointBase(Point point)
        {
            return new PointBase(point.X, point.Y);
        }

        public static implicit operator Point(PointBase pointInfoBase)
        {
            return new Point(pointInfoBase.X, pointInfoBase.Y);
        }

        //public override string ToString() => $"PointBase(x={X}, y={Y})";

        #region Statics

        /// <summary>
        /// Empty - a static property which provides an Empty rectangle.  X and Y are positive-infinity
        /// and Width and Height are negative infinity.  This is the only situation where Width or
        /// Height can be negative.
        /// </summary>
        public static PointBase Empty
        {
            get
            {
                return s_empty;
            }
        }

        #endregion Statics

        #region Private Methods  

        static private PointBase CreateEmptyRect()
        {
            PointBase point = new PointBase();
            // We can't set these via the property setters because negatives widths
            // are rejected in those APIs.
            point._x = Double.PositiveInfinity;
            point._y = Double.PositiveInfinity;
            return point;
        }

        #endregion Private Methods


        #region Private Fields

        private readonly static PointBase s_empty = CreateEmptyRect();

        #endregion Private Fields

        #region Public Methods




        /// <summary>
        /// Compares two PointBase instances for exact equality.
        /// Note that double values can acquire error when operated upon, such that
        /// an exact comparison between two values which are logically equal may fail.
        /// Furthermore, using this equality operator, Double.NaN is not equal to itself.
        /// </summary>
        /// <returns>
        /// bool - true if the two PointBase instances are exactly equal, false otherwise
        /// </returns>
        /// <param name='point1'>The first PointBase to compare</param>
        /// <param name='point2'>The second PointBase to compare</param>
        public static bool operator ==(PointBase point1, PointBase point2)
        {
            return point1.X == point2.X &&
                   point1.Y == point2.Y;
        }

        /// <summary>
        /// Compares two PointBase instances for exact inequality.
        /// Note that double values can acquire error when operated upon, such that
        /// an exact comparison between two values which are logically equal may fail.
        /// Furthermore, using this equality operator, Double.NaN is not equal to itself.
        /// </summary>
        /// <returns>
        /// bool - true if the two PointBase instances are exactly unequal, false otherwise
        /// </returns>
        /// <param name='point1'>The first PointBase to compare</param>
        /// <param name='point2'>The second PointBase to compare</param>
        public static bool operator !=(PointBase point1, PointBase point2)
        {
            return !(point1 == point2);
        }
        /// <summary>
        /// Compares two PointBase instances for object equality.  In this equality
        /// Double.NaN is equal to itself, unlike in numeric equality.
        /// Note that double values can acquire error when operated upon, such that
        /// an exact comparison between two values which
        /// are logically equal may fail.
        /// </summary>
        /// <returns>
        /// bool - true if the two PointBase instances are exactly equal, false otherwise
        /// </returns>
        /// <param name='point1'>The first PointBase to compare</param>
        /// <param name='point2'>The second PointBase to compare</param>
        public static bool Equals(PointBase point1, PointBase point2)
        {
            return Math.Abs(point1.X - point2.X) < 0.01f &&
                   Math.Abs(point1.Y - point2.Y) < 0.01f;
        }

        /// <summary>
        /// Equals - compares this PointBase with the passed in object.  In this equality
        /// Double.NaN is equal to itself, unlike in numeric equality.
        /// Note that double values can acquire error when operated upon, such that
        /// an exact comparison between two values which
        /// are logically equal may fail.
        /// </summary>
        /// <returns>
        /// bool - true if the object is an instance of PointBase and if it's equal to "this".
        /// </returns>
        /// <param name='o'>The object to compare to "this"</param>
        public override bool Equals(object o)
        {
            if ((null == o) || !(o is PointBase))
            {
                return false;
            }

            PointBase value = (PointBase)o;
            return PointBase.Equals(this, value);
        }

        /// <summary>
        /// Equals - compares this PointBase with the passed in object.  In this equality
        /// Double.NaN is equal to itself, unlike in numeric equality.
        /// Note that double values can acquire error when operated upon, such that
        /// an exact comparison between two values which
        /// are logically equal may fail.
        /// </summary>
        /// <returns>
        /// bool - true if "value" is equal to "this".
        /// </returns>
        /// <param name='value'>The PointBase to compare to "this"</param>
        public bool Equals(PointBase value)
        {
            return PointBase.Equals(this, value);
        }
        /// <summary>
        /// Returns the HashCode for this PointBase
        /// </summary>
        /// <returns>
        /// int - the HashCode for this PointBase
        /// </returns>
        public override int GetHashCode()
        {
            // Perform field-by-field XOR of HashCodes
            return X.GetHashCode() ^
                   Y.GetHashCode();
        }

        ///// <summary>
        ///// Parse - returns an instance converted from the provided string using
        ///// the culture "en-US"
        ///// <param name="source"> string with PointBase data </param>
        ///// </summary>
        //public static PointBase Parse(string source)
        //{
        //    IFormatProvider formatProvider = System.Windows.Markup.TypeConverterHelper.InvariantEnglishUS;

        //    TokenizerHelper th = new TokenizerHelper(source, formatProvider);

        //    PointBase value;

        //    String firstToken = th.NextTokenRequired();

        //    value = new PointBase(
        //        Convert.ToDouble(firstToken, formatProvider),
        //        Convert.ToDouble(th.NextTokenRequired(), formatProvider));

        //    // There should be no more tokens in this string.
        //    th.LastTokenRequired();

        //    return value;
        //}

        #endregion Public Methods

        #region Internal Properties


        /// <summary>
        /// Creates a string representation of this object based on the current culture.
        /// </summary>
        /// <returns>
        /// A string representation of this object.
        /// </returns>
        public override string ToString()
        {
            // Delegate to the internal method which implements all ToString calls.
            return ConvertToString(null /* format string */, null /* format provider */);
        }

        /// <summary>
        /// Creates a string representation of this object based on the IFormatProvider
        /// passed in.  If the provider is null, the CurrentCulture is used.
        /// </summary>
        /// <returns>
        /// A string representation of this object.
        /// </returns>
        public string ToString(IFormatProvider provider)
        {
            // Delegate to the internal method which implements all ToString calls.
            return ConvertToString(null /* format string */, provider);
        }

        /// <summary>
        /// Creates a string representation of this object based on the format string
        /// and IFormatProvider passed in.
        /// If the provider is null, the CurrentCulture is used.
        /// See the documentation for IFormattable for more information.
        /// </summary>
        /// <returns>
        /// A string representation of this object.
        /// </returns>
        string IFormattable.ToString(string format, IFormatProvider provider)
        {
            // Delegate to the internal method which implements all ToString calls.
            return ConvertToString(format, provider);
        }

        /// <summary>
        /// Creates a string representation of this object based on the format string
        /// and IFormatProvider passed in.
        /// If the provider is null, the CurrentCulture is used.
        /// See the documentation for IFormattable for more information.
        /// </summary>
        /// <returns>
        /// A string representation of this object.
        /// </returns>
        internal string ConvertToString(string format, IFormatProvider provider)
        {
            // Helper to get the numeric list separator for a given culture.
            char separator = ',';
            return String.Format(provider,
                                 "{1:" + format + "}{0}{2:" + format + "}",
                                 separator,
                                 _x,
                                 _y);
        }



        #endregion Internal Properties

        #region Public Methods

        /// <summary>
        /// Offset - update the location by adding offsetX to X and offsetY to Y
        /// </summary>
        /// <param name="offsetX"> The offset in the x dimension </param>
        /// <param name="offsetY"> The offset in the y dimension </param>
        public void Offset(double offsetX, double offsetY)
        {
            _x += offsetX;
            _y += offsetY;
        }

        /// <summary>
        /// Operator PointBase + Vector
        /// </summary>
        /// <returns>
        /// PointBase - The result of the addition
        /// </returns>
        /// <param name="point"> The PointBase to be added to the Vector </param>
        /// <param name="vector"> The Vectr to be added to the PointBase </param>
        public static PointBase operator +(PointBase point, VectorBase vector)
        {
            return new PointBase(point._x + vector._x, point._y + vector._y);
        }

        /// <summary>
        /// Add: PointBase + Vector
        /// </summary>
        /// <returns>
        /// PointBase - The result of the addition
        /// </returns>
        /// <param name="point"> The PointBase to be added to the Vector </param>
        /// <param name="vector"> The Vector to be added to the PointBase </param>
        public static PointBase Add(PointBase point, VectorBase vector)
        {
            return new PointBase(point._x + vector._x, point._y + vector._y);
        }

        /// <summary>
        /// Operator PointBase - Vector
        /// </summary>
        /// <returns>
        /// PointBase - The result of the subtraction
        /// </returns>
        /// <param name="point"> The PointBase from which the Vector is subtracted </param>
        /// <param name="vector"> The Vector which is subtracted from the PointBase </param>
        public static PointBase operator -(PointBase point, VectorBase vector)
        {
            return new PointBase(point._x - vector._x, point._y - vector._y);
        }

        /// <summary>
        /// Subtract: PointBase - Vector
        /// </summary>
        /// <returns>
        /// PointBase - The result of the subtraction
        /// </returns>
        /// <param name="point"> The PointBase from which the Vector is subtracted </param>
        /// <param name="vector"> The Vector which is subtracted from the PointBase </param>
        public static PointBase Subtract(PointBase point, VectorBase vector)
        {
            return new PointBase(point._x - vector._x, point._y - vector._y);
        }

        /// <summary>
        /// Operator PointBase - PointBase
        /// </summary>
        /// <returns>
        /// Vector - The result of the subtraction
        /// </returns>
        /// <param name="point1"> The PointBase from which point2 is subtracted </param>
        /// <param name="point2"> The PointBase subtracted from point1 </param>
        public static VectorBase operator -(PointBase point1, PointBase point2)
        {
            return new VectorBase(point1._x - point2._x, point1._y - point2._y);
        }

        /// <summary>
        /// Subtract: PointBase - PointBase
        /// </summary>
        /// <returns>
        /// Vector - The result of the subtraction
        /// </returns>
        /// <param name="point1"> The PointBase from which point2 is subtracted </param>
        /// <param name="point2"> The PointBase subtracted from point1 </param>
        public static VectorBase Subtract(PointBase point1, PointBase point2)
        {
            return new VectorBase(point1._x - point2._x, point1._y - point2._y);
        }

        /// <summary>
        /// Operator PointBase * Matrix
        /// </summary>
        //public static PointBase operator *(PointBase point, Matrix matrix)
        //{
        //    return matrix.Transform(point);
        //}

        /// <summary>
        /// Multiply: PointBase * Matrix
        /// </summary>
        //public static PointBase Multiply(PointBase point, Matrix matrix)
        //{
        //    return matrix.Transform(point);
        //}

        /// <summary>
        /// Explicit conversion to Size.  Note that since Size cannot contain negative values,
        /// the resulting size will contains the absolute values of X and Y
        /// </summary>
        /// <returns>
        /// Size - A Size equal to this PointBase
        /// </returns>
        /// <param name="point"> PointBase - the PointBase to convert to a Size </param>
        public static explicit operator SizeBase(PointBase point)
        {
            return new SizeBase(Math.Abs(point._x), Math.Abs(point._y));
        }

        /// <summary>
        /// Explicit conversion to Vector
        /// </summary>
        /// <returns>
        /// Vector - A Vector equal to this PointBase
        /// </returns>
        /// <param name="point"> PointBase - the PointBase to convert to a Vector </param>
        public static explicit operator VectorBase(PointBase point)
        {
            return new VectorBase(point._x, point._y);
        }

        #endregion Public Methods
    }
}